﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Shade.FrontEnd.Infrastructure
{
    public static class HtmlHelpers
    {
        public static MvcHtmlString SerializeToJson(this HtmlHelper helper, object serializable)
        {
            return SerializeToJson(serializable);
        }

        public static MvcHtmlString Traits(this HtmlHelper html, object model)
        {
            Type type = model.GetType();
            IEnumerable<string> props = type.GetProperties().
                                             Select(m => m.Name);

            IDictionary<string, TypeTrait> dictionary = new Dictionary<string, TypeTrait>();
            ConstructTraits(type, ref dictionary);
            return ConstructKnockoutScript(dictionary);
        }

        private static MvcHtmlString ConstructKnockoutScript(IDictionary<string, TypeTrait> traits)
        {
            StringBuilder out_string = new StringBuilder();
            foreach (KeyValuePair<string, TypeTrait> m in traits) {
                TypeTrait type = m.Value;
                StringBuilder builder = new StringBuilder();
                StringBuilder builder_prototype_refresh = new StringBuilder();
                StringBuilder builder_prototype_part_refresh = new StringBuilder();
                builder.AppendLine();
                builder_prototype_refresh.AppendLine();
                builder_prototype_part_refresh.AppendLine();

                builder.AppendLine(string.Format("window.{0} = window.{0} || function (originalModel) {1}", type.TypeName, "{"));
                builder_prototype_refresh.AppendLine(string.Format("window.{0}.prototype.Refresh = function (originalModel) {1}", type.TypeName, "{"));
                #region Property Trait
                string buf = string.Empty;
                foreach (PropertyTrait p in type.Properties) {
                    string holder = string.Empty;
                    if (p.IsArray) {
                        buf = "\tvar model = [], ";
                        builder.AppendLine(buf);
                        builder_prototype_refresh.AppendLine(buf);

                        holder = p.IsBuiltIn ? "prop_val[i]" : string.Format("new {0}(prop_val[i])", p.TypeName);
                        buf = string.Format("\t\tprop_val = originalModel && originalModel.{0} || [];", p.PropName);
                        builder.AppendLine(buf);
                        builder_prototype_refresh.AppendLine(buf);

                        buf = string.Format("\tfor(var i in prop_val) model[i] = {0};", holder);
                        builder.AppendLine(buf);
                        builder_prototype_refresh.AppendLine(buf);

                        builder.AppendLine(string.Format("\tthis.{0} = ko.observableArray(model);", p.PropName));
                        builder_prototype_refresh.AppendLine(string.Format("\tthis.{0}(model);", p.PropName));
                    } else {
                        holder = p.IsBuiltIn ?
                            string.Format("originalModel && originalModel.{0}", p.PropName) :
                            string.Format("new {0}(originalModel && originalModel.{1})", p.TypeName, p.PropName);
                        builder.AppendLine(string.Format("\tthis.{0} = ko.observable({1});", p.PropName, holder));
                        builder_prototype_refresh.AppendLine(string.Format("\tthis.{0}({1});", p.PropName, holder));
                    }
                }
                #endregion
                builder.AppendLine("};");
                builder_prototype_refresh.AppendLine("};");
                out_string.Append(builder);
                out_string.Append(builder_prototype_refresh);
            }

            return MvcHtmlString.Create(out_string.ToString());
        }

        private static MvcHtmlString SerializeToJson(object serializable)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            StringBuilder out_string = new StringBuilder();
            serializer.Serialize(serializable, out_string);

            return MvcHtmlString.Create(string.Format("'{0}'", out_string));
        }

        private static void ConstructTraits(Type type, ref IDictionary<string, TypeTrait> traits)
        {
            if (!traits.ContainsKey(type.Name)) {
                traits[type.Name] = new TypeTrait { TypeName = type.Name };
                PropertyInfo[] props = type.GetProperties();
                List<PropertyTrait> prop_traits = new List<PropertyTrait>();
                foreach (PropertyInfo m in props) {
                    PropertyTrait prop = new PropertyTrait
                    {
                        PropName = m.Name,
                        IsArray = m.PropertyType.GetGenericArguments().Length == 1
                    };
                    Type t = prop.IsArray ?
                                  m.PropertyType.GetGenericArguments().First() :
                                  m.PropertyType;

                    prop.TypeName = t.Name;
                    prop.IsBuiltIn = IsBuiltInType(t);
                    prop_traits.Add(prop);

                    if (!prop.IsBuiltIn)
                        ConstructTraits(t, ref traits);
                }
                traits[type.Name].Properties = prop_traits;
            }
        }

        private static bool IsBuiltInType(Type t)
        {
            return t.Namespace.Contains("System");
        }
    }

    public class TypeTrait
    {
        public string TypeName { get; set; }
        public IEnumerable<PropertyTrait> Properties { get; set; }
    }

    public class PropertyTrait
    {
        public string PropName { get; set; }
        public string TypeName { get; set; }
        public bool IsArray { get; set; }  //for Dictionary, we do not think it as an Array Type.
        public bool IsBuiltIn { get; set; }
    }
}
